package com.whz.mlc.provider.proxy;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author whz
 *  2021/8/13
 */
@Slf4j
public class MlcProxyFactory implements ObjectFactory<Object> {

    private final ProxyFactory proxyFactory;

    private final List<ProxyFilter> filters = Lists.newArrayList(ProxyFilter.DEFAULT_FILTER);

    /**
     * jdk proxy
     *
     * @param target     目标对象
     * @param interfaces 代理接口类型
     */
    public MlcProxyFactory(Object target, Class<?>... interfaces) {
        proxyFactory = new ProxyFactory(target);
        proxyFactory.setInterfaces(interfaces);
        addAop();
    }

    /**
     * cglib proxy
     *
     * @param target 目标对象
     */
    public MlcProxyFactory(Object target) {
        proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(true);
        addAop();
    }

    private void addAop() {
        proxyFactory.addAdvice(new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) {

                Collection<ProxyFilter> next = getProxyFilters(invocation);

                Object result = null;
                try {
                    // before
                    before(invocation, next);

                    // execute it
                    result = invoke(invocation.getThis(), invocation.getMethod(), invocation.getArguments());

                    // after
                    after(invocation, next, result);
                } catch (Throwable throwable) {
                    error(next, throwable);
                }
                return result;
            }


            private Object invoke(Object target, Method method, Object[] args) throws Throwable {
                try {
                    return method.invoke(target, args);
                } catch (InvocationTargetException e) {
                    throw e.getCause();
                }
            }
        });
    }

    private void error(Collection<ProxyFilter> next, Throwable throwable) {
        next.forEach(f -> {
            try {
                f.onError(throwable);
            } catch (Throwable ignored) {
            }
        });
    }


    private void after(MethodInvocation invocation, Collection<ProxyFilter> next, Object result) {
        next.forEach(f -> {
                    try {
                        f.onAfter(invocation, result);
                    } catch (Throwable throwable) {
                        log.error("tmc proxy after error.", throwable);
                    }
                }
        );
    }

    private void before(MethodInvocation invocation, Collection<ProxyFilter> next) {
        next.forEach(f -> {
            try {
                f.onBefore(invocation);
            } catch (Throwable throwable) {
                log.error("tmc proxy before error.", throwable);
            }
        });
    }

    private Collection<ProxyFilter> getProxyFilters(MethodInvocation invocation) {
        return filters.stream()
                .filter(f -> f.filter(invocation))
                .collect(Collectors.toList());
    }

    /**
     * 添加AOP过滤器
     *
     * @param filters AOP过滤器
     */
    public void addProxyFilters(ProxyFilter... filters) {
        this.filters.addAll(Arrays.asList(filters));
    }

    @Override
    public @NonNull Object getObject() throws BeansException {
        return proxyFactory.getProxy();
    }
}